package zdream.pmfield.stage;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;

import zdream.pmfield.core.Result;
import zdream.pmfield.data.foe.Foe;
import zdream.pmfield.stage.director.Director;
import zdream.pmfield.stage.director.MoveChecker;
import zdream.pmfield.stage.director.StageResult;
import zdream.pmfield.stage.message.ActionRequestReply;
import zdream.pmfield.stage.message.NeedReplyMessage;
import zdream.pmfield.stage.message.StageMessage;
import zdream.pmfield.stage.object.FoeActor;
import zdream.pmfield.stage.object.Seat;
import zdream.pmfield.storage.FoeTemplateStorage;

/**
 * @author Zdream
 * @date 2023-11-10
 * @since 0.0.1
 */
public class Stage {
	public static final int SKIP_TURN_MOVE_ID = 10001;
	public static final int MOVE_TO_MOVE_ID = 10002;
	private int nextId = 1;

	private Stage(StageSpec spec) {
		Objects.requireNonNull(spec);
		this.storage = Objects.requireNonNull(spec.storage);
		this.enableDistanceCheck = spec.enableDistanceCheck;
		this.accuracyAttenuationForDistance = this.enableDistanceCheck ? spec.accuracyAttenuationForDistance : 0;

		spec.foeSpecConsumer.accept(this.foeSpec);
	}

	/**
	 * maybe be null
	 */
	public final FoeTemplateStorage storage;

	/**
	 * 是否在战斗时检查技能释放的距离, 太远不让放
	 */
	public final boolean enableDistanceCheck;

	/**
	 * 战斗时默认每格距离, 准确度下降多少 (单位: 百分比)
	 */
	public final int accuracyAttenuationForDistance;

	/**
	 * 对于 A 方和 B 方都有 3 人的场景,
	 * seats=[空, A1, A2, A3, 空, B3, B2, B1]
	 *
	 * 允许在战斗途中动态删除和增加 seat
	 */
	public Seat[] seats;
	/**
	 * 放全场场地效果
	 */
	public Seat globalSeat;
	public final List<FoeActor> allFoes = new ArrayList<>();

	public Timeline timeline;
	public Director director;
	public MoveChecker moveChecker;
	private boolean directorFinished;
	private StageResult result;

	/**
	 * 方法 {@link #run()} 返回的 {@link StageMessage} 中,
	 * 有些是需要你反馈之后才能往下进行的.
	 *
	 * 这类 message 将存在这里, 等到反馈结束后置为 null,
	 * run 方法才能继续往下执行.
	 */
	private NeedReplyMessage<?> needReply;

	private final FoeActor.FoeActorSpec foeSpec = new FoeActor.FoeActorSpec();

	public FoeActor createFoe(Foe foe) {
		FoeActor foeActor = new FoeActor(foe, this, foeSpec);
		this.allFoes.add(foeActor);
		return foeActor;
	}

	public Seat findSeatByFoe(Foe foe) {
		return allFoes.stream()
				.filter(foeActor -> foeActor.foe == foe)
				.findFirst()
				.map(foeActor -> foeActor.seat)
				.orElse(null);
	}

	public Seat findSeatByX(int x) {
		for (Seat seat : seats) {
			if (seat.x == x) {
				return seat;
			}
		}
		return null;
	}

	public FoeActor findFoe(Foe foe) {
		return allFoes.stream()
				.filter(foeActor -> foeActor.foe == foe)
				.findFirst()
				.orElse(null);
	}

	/**
	 * <pre>
	 * while ((message = stage.run()) != null) {
	 *   // handle message
	 * }
	 * </pre>
	 * 该方法线程不安全.
	 */
	public StageMessage run() {
		if (needReply != null) {
			return needReply;
		}

		while (true) {
			final StageMessage message = fetchFirstMessageFromQueue();
			if (message != null) {
				return message;
			}

			if (directorFinished) {
				break;
			}

			director.run();
		}

		return null;
	}

	private StageMessage fetchFirstMessageFromQueue() {
		final StageMessage first = director.popMessage();
		if (first == null) {
			return null;
		}

		if (first.needReply()) {
			this.needReply = first.caseToNeedReplyMessage();
			final Function<ActionRequestReply, Result> validator = this.needReply.validator;

			// 如果 reply 被成功提交, needReply 清空
			this.needReply.validator = reply -> {
				Result result = validator.apply(reply);
				if (result.success) {
					this.needReply = null;
				}
				return result;
			};
		}
		return first;
	}

	public boolean hasMessageNeedReply() {
		return needReply != null;
	}

	public StageMessage popMessage() {
		return director.popMessage();
	}

	public void pushMessage(StageMessage message) {
		director.pushMessage(message);
	}

	public int getNextIdAndIncrease() {
		return nextId++;
	}

	public float now() {
		return timeline.round;
	}

	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder(100);

		if (seats != null) {
			builder.append("位置:\n");
			for (Seat seat : seats) {
				builder.append(seat).append('\n');
			}
		}

		return builder.toString();
	}

	public boolean isFinished() {
		return directorFinished && !director.hasMessageRemain();
	}

	public StageResult getResult() {
		return result;
	}

	protected static class StageSpec {
		protected FoeTemplateStorage storage;
		protected Consumer<FoeActor.FoeActorSpec> foeSpecConsumer = (spec) -> {};
		protected Function<Stage, MoveChecker> moveCheckerFactory = MoveChecker::new;
		protected boolean enableDistanceCheck = true;
		protected int accuracyAttenuationForDistance = 3;
	}

	public static StageBuilder builder() {
		return new StageBuilder();
	}

	public static class StageBuilder {
		protected Stage stage;
		protected final StageSpec spec = new StageSpec();
		private final List<Foe> foesForOneSides = new ArrayList<>();
		private final List<Foe> foesForAnotherSides = new ArrayList<>();

		protected StageBuilder() {
		}

		public StageBuilder putFoeToOneSide(Foe... foes) {
			foesForOneSides.addAll(Arrays.asList(foes));
			return this;
		}

		public StageBuilder putFoeToAnotherSide(Foe... foes) {
			foesForAnotherSides.addAll(Arrays.asList(foes));
			return this;
		}

		public StageBuilder withStorage(FoeTemplateStorage storage) {
			this.spec.storage = storage;
			return this;
		}

		public StageBuilder withAccuracyAttenuation(int accuracyAttenuation) {
			this.spec.accuracyAttenuationForDistance = Math.max(accuracyAttenuation, 0);
			return this;
		}

		public StageBuilder disableDistanceCheck() {
			this.spec.enableDistanceCheck = false;
			return this;
		}

		public Stage build() {
			Objects.requireNonNull(spec.storage, "创建 Stage 时 storage 必须指定");
			initSpec();
			this.stage = new Stage(spec);
			putFoesToSeats();
			initTimeline();
			initDirector();
			initFoesAndSeats();
			return stage;
		}

		protected void initSpec() {
			spec.foeSpecConsumer = spec.foeSpecConsumer.andThen(foeSpec -> {
				foeSpec.stageMovesSupplier = () -> Arrays.asList(
					spec.storage.findMoveById(SKIP_TURN_MOVE_ID, 1),
					spec.storage.findMoveById(MOVE_TO_MOVE_ID, 1)
				);
			});
		}

		private void putFoesToSeats() {
			int amount = foesForOneSides.size() + foesForAnotherSides.size();
			int offset = -foesForOneSides.size() - 1;

			// [] [我2] [我1] [x=0] [敌1] [敌2] []
			Seat[] seats = stage.seats = new Seat[amount + 3];
			int id = 1;
			for (int i = 0; i < seats.length; i++) {
				Seat seat = new Seat(id, stage);
				seats[i] = seat;
				++id;
				seat.x = i + offset;

				if (i != 0) {
					seat.leftJoin(seats[i - 1]);
				}
			}

			int index = foesForOneSides.size();
			for (Foe foe: foesForOneSides) {
				FoeActor foeActor = stage.createFoe(foe);
				foeActor.side = 1;
				foeActor.arrive(seats[index]);
				--index;
			}

			index = foesForOneSides.size() + 2;
			for (Foe foe: foesForAnotherSides) {
				FoeActor foeActor = stage.createFoe(foe);
				foeActor.side = 2;
				foeActor.arrive(seats[index]);
				++index;
			}

			stage.globalSeat = new Seat(id, stage);
			stage.globalSeat.global = true;
		}

		private void initTimeline() {
			stage.timeline = new Timeline(stage);
			stage.timeline.init();
		}

		private void initDirector() {
			stage.director = new Director(stage);
			stage.director.init();
			stage.director.setFinishedHandler((result) -> {
				stage.directorFinished = true;
				stage.result = result;
			});
			stage.moveChecker = spec.moveCheckerFactory.apply(stage);
		}

		private void initFoesAndSeats() {
			stage.globalSeat.init();
			for (Seat seat : stage.seats) {
				seat.init();
			}
			for (FoeActor foe : stage.allFoes) {
				foe.init();
			}
		}
	}
}
